/**
 * @file
 * @author dousha
 * @date 2020
 */

#include "stm32f4xx.h"

#include "task.h"
#include "../memory/memory.h"
#include "../peripheral/clock.h"
#include "../../user/idle_task.h"
#include "../interrupt/interrupt.h"
#include "../../user/tasks.h"
#include "schedule.h"

TaskControlBlock *currentTask = NIL;
TaskControlBlock *nextTask = NIL;

static TaskManager *taskManager = NIL;

static uint32_t lastSwitchingTime = 0u;

static ProcessId lastAllocatedProcessId = 0u;

static bool taskNeedSwitching = false;

/**
 * 初始化任务结构。
 *
 * @kernelOnly
 */
void task_init() {
	taskManager = memory_allocate(sizeof(TaskManager));
	taskManager->sliceLength = 5; // 每 50us 进行一次任务调度
	taskManager->schedules = memory_allocate(sizeof(TaskSchedule));
	taskManager->totalTasks = 0;
	taskManager->currentSchedule = NIL;
	taskManager->tailSchedule = NIL;
	ProcessId pid = task_spawn(idle_task, TASK_PERSISTENT);
	if (pid < 1) {
		for (;;) {
			__asm volatile("nop");
		}
	}
	user_task_setup();
	user_task_enter();
}

/**
 * 将下一个任务的信息加载到汇编可以读取的地方。
 *
 * 这个函数内不需要关中断，因为已经在汇编内关闭了中断。
 * 
 * @see task_tick
 * 
 * @invokedBy{pending_service_handler}
 */
void task_switch() {
	taskNeedSwitching = false;
	if (nextTask == NIL) {
		// task_tick 没有要求进行任务切换
		return;
	}
	currentTask = nextTask;
	nextTask = NIL;
}

/**
 * 测试是否需要切换任务。
 *
 * @kernelOnly
 *
 * @return 是否需要切换任务
 */
Result task_need_switching() {
	return taskNeedSwitching;
}

/**
 * 标记在下一个 SysTick 发生时进行任务切换。
 * 
 * 这个函数的典型应用是当前函数主动移交执行权或者退出时由 service_handler 调用。
 *
 * @kernelOnly
 */
void task_mark_switch() {
	taskNeedSwitching = true;
}

/**
 * 当任务退出时执行清理。
 * 
 * @kernelOnly 用户程序请调用 terminate() 退出。
 */
void task_cleanup() {
	// TODO
	for (;;) {
		__asm volatile ("nop");
	}
}

/**
 * 初始化任务堆栈。
 * 
 * @param sp 栈指针
 * @param pc 指令指针
 * @return void* 新的栈顶
 */
static volatile void* task_stack_initialize(volatile void *sp, volatile void *pc) {
	uint32_t *taskStackPointer = (uint32_t *) sp;
	taskStackPointer[-17] = 0; // R4
	taskStackPointer[-16] = 0; // R5
	taskStackPointer[-15] = 0; // R6
	taskStackPointer[-14] = 0; // R7
	taskStackPointer[-13] = 0; // R8
	taskStackPointer[-12] = 0; // R9
	taskStackPointer[-11] = 0; // R10
	taskStackPointer[-10] = 0; // R11
	taskStackPointer[-9] = EXCEPTION_RETURN_PSP_NO_FP; // EXC_RETURN
	taskStackPointer[-8] = 0; // R0
	taskStackPointer[-7] = 0; // R1
	taskStackPointer[-6] = 0; // R2
	taskStackPointer[-5] = 0; // R3
	taskStackPointer[-4] = 0; // R12
	taskStackPointer[-3] = (uint32_t) task_cleanup; // LR
	taskStackPointer[-2] = (uint32_t) pc; // PC
	taskStackPointer[-1] = (1u << 24); // xPSR
	return sp - 17 * 4;
}

/**
 * 将当前任务向后调度。
 * 
 * 这个函数不检查当前任务是否是活动的！
 * 
 * @kernelOnly 用户程序请调用 yield() 放弃时间片。
 */
void task_reschedule_current_task() {
	// 将当前任务安插到合适的位置上
	TaskSchedule *scheduleIndex = taskManager->schedules;
	if (currentTask->state == TASK_RUNNING) {
		currentTask->state = TASK_READY;
	}
	if (currentTask->state == TASK_TERMINATED) {
		if (currentTask->flags & TASK_PERSISTENT) {
			// 持久任务不得退出！
			// 重启该任务
			currentTask->sp = currentTask->bp;
			task_stack_initialize(currentTask->sp, currentTask->pc);
			currentTask->flags = TASK_CREATED;
		} else {
			// 清理任务
			// taskManager->schedules 指针总是指向空闲任务
			// 而空闲任务是永不退出的，所以可以保证该指针恒有效。
			memory_free((void *) currentTask->sp);
			memory_free(currentTask);
			while (scheduleIndex != NIL) {
				if (scheduleIndex->next == taskManager->currentSchedule) {
					scheduleIndex->next = taskManager->currentSchedule->next;
				}
				scheduleIndex = scheduleIndex->next;
			}
			memory_free(taskManager->currentSchedule);
			taskManager->totalTasks--;
		}
	} 
	// 寻找下一个合适的任务
	TaskSchedule *nextSchedule = NIL;
	while (scheduleIndex != NIL) {
		if (scheduleIndex == taskManager->currentSchedule) {
			scheduleIndex = scheduleIndex->next;
			continue;
		}
		if (clock_ticks_from_now(scheduleIndex->executionDecade, scheduleIndex->executionTime) >= 0) {
			if (nextSchedule == NIL) {
				nextSchedule = scheduleIndex;
			} else {
				// 低谦让度的任务会抢断高谦让度
				// FIXME: 如果高谦让度的任务一直抢断低谦让度，那么低谦让度的任务将一直得不到执行
				// 要避免进程饥饿 (starve) 问题，则需要更聪明的调度算法
				// 一个最简单的实现是将时间差也作为抢断参数之一，时间差越大，则优先级越高
				if (nextSchedule->task->niceness > scheduleIndex->task->niceness) {
					nextSchedule = scheduleIndex;
				}
			}
		}
		scheduleIndex = scheduleIndex->next;
	}
	if (nextSchedule != NIL) {
		nextTask = nextSchedule->task;
		taskManager->currentSchedule = nextSchedule;
		taskNeedSwitching = true;
	} else {
		// 没有下一个任务，恢复任务运行
		// XXX: 这个任务有可能已经结束且被释放，但是目前而言，总是存在空闲任务接手
		// 这个分支通常是只有空闲任务在运行时才会进入
		currentTask->state = TASK_RUNNING;
		taskNeedSwitching = false;
	}
}

/**
 * 通知任务子系统 SysTick 发生了。
 *
 * @kernelOnly
 */
void task_tick() {
	uint32_t decade = clock_decade_get();
	uint32_t time = clock_get();
	if (currentTask == NIL) {
		// 任务系统尚未初始化，不做操作
		return;
	}
	// 检查当前任务是否需要调出
	if (currentTask->state == TASK_IO_WAITING || currentTask->state == TASK_TIMED_WAITING || currentTask->state == TASK_TERMINATED) {
		task_reschedule_current_task();
	} else if (currentTask->state == TASK_CREATED) {
		// 当前任务如果是新创建的任务，那么该任务现在就绪，并开始执行，即使它的优先级可能低于其他进程
		currentTask->state = TASK_RUNNING;
		taskManager->currentSchedule->executionDecade = decade;
		taskManager->currentSchedule->executionTime = time;
		//taskNeedSwitching = true;
	} else {
		// 当前任务如果是正在运行的状态，那么检查该任务是否需要调出
		int32_t thisTaskRunningTime = clock_ticks_from_now(taskManager->currentSchedule->executionDecade, taskManager->currentSchedule->executionTime);
		if (thisTaskRunningTime >= taskManager->sliceLength)
		{
			task_reschedule_current_task();
		}
	}
}

/**
 * 启动一个任务。
 * 
 * @param task 任务函数
 * @param flags 任务标记
 * 
 * @return 任务 ID. 如果任务创建失败，则返回 0.
 */ 
ProcessId task_spawn(TaskPointer task, uint8_t flags) {
	interrupt_disable();
	void *taskStack = memory_allocate(MEMORY_ALIGN_SIZE * TASK_STACK_SIZE);
	if (taskStack == NIL) {
		interrupt_enable();
		return 0;
	}
	ProcessId pid = ++lastAllocatedProcessId;
	TaskControlBlock *tcb = memory_allocate(sizeof(TaskControlBlock));
	tcb->flags = 0;
	// 将栈指针指向栈底（栈向低地址生长）
	tcb->bp = tcb->sp = taskStack + MEMORY_ALIGN_SIZE * TASK_STACK_SIZE;
	tcb->pc = task;
	tcb->tid = pid;
	tcb->time = 1;
	tcb->niceness = 0xffu;
	tcb->state = TASK_CREATED;
	tcb->sp = task_stack_initialize(tcb->sp, tcb->pc);
	tcb->flags = flags;
	TaskSchedule *currentSchedule = memory_allocate(sizeof(TaskSchedule));
	currentSchedule->executionDecade = clock_decade_get();
	currentSchedule->executionTime = clock_get() + 1;
	currentSchedule->next = NIL;
	currentSchedule->task = tcb;
	if (taskManager->tailSchedule == NIL) {
		// 创建为首个任务
		taskManager->schedules = taskManager->currentSchedule = taskManager->tailSchedule = currentSchedule;
		currentTask = tcb;
	} else {
		// 追加任务
		taskManager->tailSchedule->next = currentSchedule;
		taskManager->tailSchedule = currentSchedule;
	}
	taskManager->totalTasks++;
	interrupt_enable();
	return pid;
}

/**
 * 设置当前任务的下一次执行时间。
 * 
 * @param decade 周期
 * @param time 时间
 * 
 * @kernelOnly
 */
void task_reschedule(uint32_t decade, uint32_t time) {
	taskManager->currentSchedule->executionDecade = decade;
	taskManager->currentSchedule->executionTime = time;
}

/**
 * 设置当前任务为 IO 等待状态。
 * 
 * @driverOnly
 */
void task_mark_io_wait() {
	currentTask->state = TASK_IO_WAITING;
}

/**
 * 设置当前的任务为定时等待状态。
 * 
 * @driverOnly
 */
void task_mark_timed_wait() {
	currentTask->state = TASK_TIMED_WAITING;
}

/**
 * 设置 PendSV 位准备进行任务切换。
 * 
 * @kernelOnly
 */
void task_pend_switch() {
	SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk;
}
